風和日麗的一天,帶著愉快學習的心情,提早半小時就到公司了。早起的鳥兒有蟲吃,早起的 Yoyo 有書唸,繼續閱讀《Effective C++》的第二章吧!
首先簡單解釋 知識點 7 提到的「基底類別」和「衍生類別」。兩者皆是物件導向程式設計中的概念,用於描述類別之間的繼承關係。
在執行期間 object 的型別會從 base class 轉換到 derived class,並於解構銷毀時再回到 base class。
當 derived class 的物件開始初始化時,會先執行 base class 的建構子,此時物件便不算是 derived class。若在在這個階段呼叫虛擬函數,系統只會執行 base class 版本的函數。只有等 derived class 的建構子開始執行並完成初始化後,虛擬函數才會指向 derived class 的版本。
因此,base class 的建構子及解構子中均無法執行 derived class 的虛擬函數。為了避免重複程式碼,常見的做法是將建構子的主要任務提取到 init()
函數中。不過需注意此 init()
函數裡依舊不能呼叫虛擬函數。
*this
賦值的過程是 right-associative。舉例來說,下方的 chain of assignments:
int x, y, z;
int x = y = z = 15;
亦可寫為:
x = (y = (z = 15));
進行賦值操作時,回傳的是 left-hand argument 的 reference,這使得賦值運算能夠實現 chain 的效果。基於此約定,我們在實作 assignment operator 時也應遵循這個慣例,確保程式行為的一致性:
class Widget
{
public:
Widget& operator= (const Widget& rhs)
{
...
return *this;
}
}
operator=
雖然聽起來很不可思議,但實務上我們很常隱晦地把 object 賦值給它自己:
a[i] = a[j];
*px = *py;
事實上,即使是不同型別的 object 依舊能引用或指向到同一個 object,例如 base class 與 derived class。為了確保 self-assignment 的安全性,可以使用 identity test,在 operator=
時先檢查是否為指派給自己:
Widget& Widget::operator=(const Widget& rhs)
{
if(this == &rhs) return *this;
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
若想更進一步確保 exception-safe,書中有提到這種寫法:
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap *pOrig = pb;
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
也可以採用 copy and swap 的小技巧,讓程式更有效率:
Widget& Widgget::operator=(Widget rhs)
{
swap(rhs);
return *this;
}
private
,因此正確做法是讓 derived class 調用 base class 的 copy constructor。知識點 5 有稍微介紹過 copy constructor 和 copy assignment operator 的呼叫時機:copy constructor會創建一個新的 object;而 copy assignment operator 則用來修改一個已存在的 object。因此請特別留意兩者不應互相呼叫!